#!/usr/bin/env python
# encoding: utf-8

import serial
import serial.tools.list_ports
import binascii
import logging
import time
import os
import platform
import re
import requests
import json
import configparser
from threading import Thread
import threading
logging.basicConfig(filename=os.getcwd() + '/log/mqtt.log',
                    format='%(asctime)s - %(levelname)s - %(message)s',
                    level=logging.DEBUG)

class Ser:
    def __init__(self, app=None, recv_buf=None, mqtt=None):
        # type: (Flask) -> None
        self.app = app
        # self.configfile = os.getcwd()+'/conf/serial.conf'
        self.serial = serial.Serial()
        self.recv_buf= recv_buf
        self.mqtt = mqtt

        # 定义线程锁
        self._on_message = None
        self._callback_mutex = threading.RLock()
        if app is not None:
            self.init_app(app)

    def init_app(self, app):
        self.serial.timeout  = app.config.get("SERIAL_TIMEOUT")
        self.serial.port     = app.config.get("SERIAL_PORT")
        self.serial.baudrate = app.config.get("SERIAL_BAUDRATE")
        self.serial.bytesize = app.config.get("SERIAL_BYTESIZE")
        self.serial.parity   = app.config.get("SERIAL_PARITY")
        self.serial.stopbits = app.config.get("SERIAL_STOPBITS")

        self.serial_alive = False

        self._open()

    # def message_callback_add(self, callback):
        # if callback is None :
            # raise ValueError("sub and callback must both be defined.")

        # with self._callback_mutex:
            # self._on_message_filtered[sub] = callback

    # @property
    # def on_message(self):
        # return self._on_message

    # @on_message.setter
    # def on_message(self, func):
        # print('消息测试1111')
        # with self._callback_mutex:
            # self._on_message = func

    # def on_recv(self):
        # def decorator(handler):
            # # type: (Callable) -> Callable
            # self.on_message = handler
            # # self.serial.read = handler
            # return handler

        # return decorator

    def find(self):
        """查询串口"""
        p = None
        port_list = serial.tools.list_ports.comports()
        port_list_info = []
        for p in port_list:
            print('*'*50)
            print(str(p.device))
            print(str(p.serial_number))
        print('*' * 50)

    def set_default_serial(self, device='', serial_number='', baudrate=''):
        """设置默认串口"""
        try:
            config = configparser.RawConfigParser()
            # 添加condition选项
            config.add_section('serial')
            config.set("serial", "device", device)
            config.set("serial", "serial_number", serial_number)
            config.set("serial", "baudrate", baudrate)
            # 写入并保存文件到当前项目文件夹下
            with open(self.configfile, 'w') as configfile:
                config.write(configfile)
        except Exception as e:
            print(e)

    def _open(self):
        """打开串口
        采用configparser读取配置文件：/conf/serial.ini, 从中获取串口设备的硬件ID[serial_number]，
        再根据当前查询到的串口外设[port_list]进行比对，打开满足设定的串口外设
        """
        # 读取默认串口配置文件
        if self.serial.port and self.serial.baudrate:
            try:
                self.serial.open()
            except serial.SerialException as e:
                print("Open Seial Error ：%s" % e)
                logging.debug("Open Seial Error ：%s"%e)
            else:
                self.serial_alive = True
                # self.thread_recv = Thread(target=self._recv)
                # self.thread_recv.setDaemon(True)
                # self.thread_recv.start()
                print("打开串口: %s / %s"%(self.serial.port, self.serial.baudrate))
                logging.debug("打开串口: %s / %s"%(self.serial.port, self.serial.baudrate))
        else:
            print("串口设备未发现")
            logging.debug("串口设备未发现")

    def _recv(self):
        """串口接收, 串口端口异常情况下，关闭串口，并重新开启串口查询功能"""
        run = True
        while run:
            while run:
                try:
                    b = self.serial.read(255)
                    if not b:
                        break
                    print(b)
                    # bytes转为16进制字符串
                    s = str(binascii.b2a_hex(b).decode('utf-8')).upper()
                    self.recv_buf.append(s)
                    self.mqtt.publish("4e010001", str(self.recv_buf))
                    # self.on_message()

                    print(self.recv_buf)
                    # cmd_list = [c for c in b]
                        # self.cmd_deal(s)
                except Exception as e:
                    print(e)
                    logging.warning(e)

    def on_message(self, func):
        def decorator(buf):
            buf = None
            try:
                if self.serial_alive:
                    b = self.serial.read(255)
                    if b:
                        buf = b
            except Exception as e:
                # pass
                print(str(e))
            return func(buf)
        return decorator
    # def on_recv(self, msg):
        # def decorator(handler):
            # # type: (Callable) -> Callable
            # self.serial.read = handler
            # # self.client.on_message = handler
            # return handler
        # return decorator

    def cmd_deal(self, recv):
        '''串口处理命令，监测校验和是否正确'''
        if recv.startswith('AA'):
            data = recv[4:-2]
            theory_len =  int(recv[2:4], 16)
            real_len = int(len(data)/2)
            if theory_len == real_len:
                data_list = [int(data[i:i+2], 16) for i in range(0,len(data)) if i%2==0]
                s = hex(sum(data_list) % 256).replace('0x','').zfill(2)[-2:]
                _sum = recv[-2:]
                if s == _sum:
                    addr    = data[0:8]
                    _type   = data[8:10]
                    cmd     = data[10:12]
                    content = data[12:]
                    if addr == localhost_cid:
                        if cmd == "B3" and content:
                            print("获取节点连接请求")
                            self.cmd_send(
                                target=content,
                                _type='01',
                                cmd='B3',
                            )
                            # 节点添加设备
                            nodes.online(cid=content.lower())
                    if cmd == "B0":
                        print('接收到心跳包')
                        nodes.heart_update(addr.lower())
                        #  sql = Sql()
                        #  sql.modify_node_alive(cid=addr,alive=True)
        return False

    def cmd_send(self, lora_addr='', target='',local='',_type='',cmd='',content=''):
        header = 'AA'
        data = target+local+_type+cmd+content
        length = hex(int(len(data)/2)).replace('0x','').zfill(2)[-2:]
        if len(data)>0 and len(data) % 2 == 0:
            data_list = [int(data[i:i+2], 16) for i in range(0,len(data)) if i%2==0]
            _sum = hex(sum(data_list) % 256).replace('0x','').zfill(2)[-2:]
            cmd = lora_addr+header+length+data+_sum
            print(cmd)
            try:
                self.write(cmd.upper(), 'hex')
            except Exception as e:
                print(e)

    def heartCheckThread(self):
        """心跳监测"""
        while self.heart_check_alive:
            time.sleep(3)
            for h in self.nodes_heart:
                print(h)
                print(time.time()- h['time'])
            #  self.nodes_heart

    def recv_deal(self):
        '''串口处理命令，监测校验和是否正确'''
        self.recv = self.recv
        start = self.recv.find("AA")
        startwith = self.recv.startswith('AA')
        def find_last(string, s):
            last_position = -1
            while True:
                position = string.find(s, last_position + 1)
                if position == -1:
                    return last_position
                last_position = position
        start_num = len(re.compile('.*?(AA).*?').findall(self.recv))
        if self.recv.startswith('AA'):
            for i in re.compile('.*?(AA).*?').findall(self.recv):
                if not len(self.recv)>4:
                    return
                else:
                    theory_len = int(self.recv[2:4], 16)
                    real_len = len(self.recv) / 2 - 3
                    if not theory_len <= real_len:
                        return
                    else:
                        end = (theory_len+3)*2
                        cmd = self.recv[start:end]
                        self.recv = self.recv[end:]
                        s = 0
                        cmd_sum = cmd[-2:]
                        cmd_data = cmd[4:-2]
                        for i in range(0, len(cmd_data)):
                            if i % 2 == 0:
                                s = s + int(cmd_data[i:i + 2], 16)
                        s = hex(s).upper()[-2:]
                        if s == cmd_sum :
                            print('校验和正确')
                            self.cmd_deal(cmd_data)
                        else:
                            self.recv = ''
                            print('校验错误！！！！！！！！')
                            print('正确：', s, '错误:', cmd_sum)
        else:
            self.recv = self.recv[self.recv.find('AA'):]

    def str_to_hex(self, s):
        """字符串转16进制"""
        return ''.join([hex(ord(c)).replace('0x', '').zfill(2) for c in s])

    def write(self, msg, base='str'):
        """串口写入msg,写入需要将字符串转换成bytes"""
        if self.serial_alive:
            if self.serial.isOpen():
                # 十六进制格式字符串转bytes
                # print(type(msg))
                if base == 'str':
                    self.serial.write(msg.encode('utf-8'))
                if base == 'hex':
                    self.serial.write(binascii.unhexlify(msg))
                print("串口发送命令：%s"%msg)
        else:
            print("serial is not open!!!")

    def writeBytes(self, msg):
        """串口写入msg,写入需要将字符串转换成bytes"""
        if self.serial_alive:
            if self.serial.isOpen():
                try:
                    self.serial.write(msg)
                    print("串口发送命令：%s"%msg)
                except Exception as e:
                    print(e)
        else:
            print("serial is not open!!!")

    def sendToUrl(self, msg):
        print("发送消息到网页")
        url = 'http://localhost:5000/serial_recv'
        body = {"msg": msg}
        headers = {'content-type': "application/json"}
        response = requests.post(url, data = json.dumps(body), headers = headers)

